
#include "SDL.h"
#include "SDL_opengl.h"
#include "SDL_image.h"

#include "TextureManager.h"
#include "LogUtil.h"

__BEGIN_NAMESPACE_DECL_

// Initialize the singleton to NULL
TextureManager* TextureManager::manager_ = NULL;

TextureManager::TextureManager()
    : disabled_(false)
{
    SDL_version compile_version;
    const SDL_version *link_version = IMG_Linked_Version();

    SDL_IMAGE_VERSION(&compile_version);
    DEBUG << "compiled with SDL_image version: "
          << (int)compile_version.major << "."
          << (int)compile_version.minor << "."
          << (int)compile_version.patch;

    DEBUG << "running with SDL_image version: "
          << (int)link_version->major << "."
          << (int)link_version->minor << "."
          << (int)link_version->patch;

    // The following requires SDL_image 1.2.10
    /*
    // Initialize the SDL_image library and at the same time,
    // check what type of support we have.
    bool failure = false;
    int initted = IMG_Init(0);
    
    // Print out a warning if we don't have JPG support.
    if ((initted & IMG_INIT_JPG) != IMG_INIT_JPG) {
        WARN << "IMG_Init: Failed to init required JPG support!\n");
        failure = true;
    }

    // Print out a warning if we don't have PNG support.
    if ((initted & IMG_INIT_JPG) != IMG_INIT_PNG) {
        WARN << "IMG_Init: Failed to init required PNG support!\n");
        failure = true;
    }

    if (failure)
        WARN << "One or more image loaders are not available, "
                "some textures may not work!"; 
    */
}

TextureManager&
TextureManager::getInstance()
{
    if (manager_ == NULL)
        manager_ = new TextureManager();

    return *manager_;
}

bool
TextureManager::loadTexture(const char *filename, unsigned int& textureId)
{
    SDL_Surface* img = NULL;
    SDL_Surface* texture = NULL;

    img = IMG_Load(filename);
    if (img == NULL)
    {
        ERROR << "Failed to load image from file: " << filename;
        return false;
    }

#ifndef GL_VERSION_2_0
    //  OpenGL 2.0 lifts the restriction that texture size must be a power of two
    for (k=1; k < img->w; k*=2);
    if (k != img->w) 
    {
        ERROR << filename << " image width not a power of two: " << img->w;
        return false;
    }
    for (k=1; k < img->h; k*=2);
    if (k != img->h)
    {
        ERROR << filename << " image height not a power of two: " << img->h;
        return false;
    }
#endif

    // The following code is based on the post by Perost here:
    // http://www.gamedev.net/community/forums/topic.asp?topic_id=453111&forum_id=61&gforum_id=0
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    Uint32 rmask = 0xff000000;
    Uint32 gmask = 0x00ff0000;
    Uint32 bmask = 0x0000ff00;
    Uint32 amask = 0x000000ff;
#else
    Uint32 rmask = 0x000000ff;
    Uint32 gmask = 0x0000ff00;
    Uint32 bmask = 0x00ff0000;
    Uint32 amask = 0xff000000;
#endif

    // Since an SDL_Surface starts with (0,0) at the top left 
    // and OpenGL expects (0,0) at the bottom left, we have to
    // swap rows so that the image is not inverted
    Uint8 *rowhi = (Uint8 *)img->pixels;
    Uint8 *rowlo = (Uint8 *)img->pixels + (img->pitch * img->h) - img->pitch;
    Uint8 *temprow = new Uint8[img->pitch];
    
    for (int i=0; i < img->h/2; i++)
    {
        memcpy (temprow, rowhi, img->pitch);
        memcpy (rowhi, rowlo, img->pitch);
        memcpy (rowlo, temprow, img->pitch);
        rowhi += img->pitch;
        rowlo -= img->pitch;
    }
    delete temprow;
        
    // Here is where the magic happens. Create a new surface that is 32 bpp.
    texture = SDL_CreateRGBSurface(SDL_SWSURFACE, img->w, img->h, 32, rmask, gmask, bmask, amask);
        
    // Do something with the alphachannel of the loaded image.
    SDL_SetAlpha(img, 0, SDL_ALPHA_OPAQUE);
        
    // blit the loaded image onto the newly created surface
    SDL_BlitSurface(img, NULL, texture, NULL);
        
    GLenum internal_format;
    GLenum img_format, img_type;

    switch (texture->format->BitsPerPixel)
    {
        case 32: 
            img_format = GL_RGBA; 
            img_type = GL_UNSIGNED_BYTE;
            internal_format = GL_RGBA8; 
            break;
        case 24: 
            img_format = GL_RGB; 
            img_type = GL_UNSIGNED_BYTE;
            internal_format = GL_RGB8;
            break;
        case 16: 
            img_format = GL_RGBA; 
            img_type = GL_UNSIGNED_SHORT_5_5_5_1;
            internal_format = GL_RGB5_A1; 
            break;
        default: 
            img_format = GL_LUMINANCE; 
            img_type = GL_UNSIGNED_BYTE;
            internal_format=GL_LUMINANCE8; 
            break;
    }
        
    int err = glGetError();
    if (err)
    {
       ERROR << gluErrorString(err) << " " << "[loadTexture]";
    }

    //  Generate 2D texture
    glGenTextures (1, &textureId);
    glBindTexture (GL_TEXTURE_2D, textureId);

    DEBUG << "Bound texture id " << textureId;
        
    //  Scale linearly when image size doesn't match
    //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    //glTexImage2D (GL_TEXTURE_2D, 0, internal_format, texture->w, texture->h, 0, 
    //              img_format, img_type, texture->pixels);

    gluBuild2DMipmaps (GL_TEXTURE_2D, internal_format, texture->w, texture->h, 
                       img_format, img_type, texture->pixels);

    SDL_FreeSurface(img);
    SDL_FreeSurface(texture);

    if (glGetError())
    {
        ERROR << "Error in glTexImage2D " << filename << " " << img->w << "x" << img->h;
        return false;
    }

    std::string file = filename;
    texturesMap_[file] = textureId;

    return true;
}

bool
TextureManager::getTexture(const char *filename, GLuint& textureId, 
                           bool load=false)
{
    if (disabled_)
    {
        // if textures are disabled, return the special tex
        // id zero.  This is the easiest way to disable textures
        // globally since we use the TextureManager in all places
        textureId = 0;
        return true;
    }

    std::string file = filename;
    if (texturesMap_.find(file) != texturesMap_.end())
    {
        textureId = texturesMap_[file];
        return true;
    }
    else if (load)
        return loadTexture(filename, textureId);
    else
        return false;
}

void TextureManager::setDisabled (bool disabled) { disabled_ = disabled; }
bool TextureManager::isDisabled () const { return disabled_; }

__END_NAMESPACE_DECL_
